import { Vec3, director } from "cc";
import { WsClient } from "tsrpc-browser";
import { GameMgr, GameMode } from "../../module_basic/scripts/GameMgr";
import { SubGameMgr } from "../../module_basic/scripts/SubGameMgr";
import { UserMgr } from "../../module_basic/scripts/UserMgr";
import { WorldMgr } from "../../module_basic/scripts/WorldMgr";
import { ServiceType } from "../../module_basic/shared/protocols/serviceProto_worldServer";
import { IBilliardsGameData, IBilliardsPlayer } from "../../module_basic/shared/protocols/worldServer/billiards/BilliardsTypeDef";
import { MsgAdjustCue } from "../../module_basic/shared/protocols/worldServer/billiards/MsgAdjustCue";
import { MsgGameBeginPush } from "../../module_basic/shared/protocols/worldServer/billiards/MsgGameBeginPush";
import { MsgGameDataChangedPush } from "../../module_basic/shared/protocols/worldServer/billiards/MsgGameDataChangedPush";
import { MsgGameDataSyncPush } from "../../module_basic/shared/protocols/worldServer/billiards/MsgGameDataSyncPush";
import { MsgGameOverPush } from "../../module_basic/shared/protocols/worldServer/billiards/MsgGameOverPush";
import { MsgHitBall } from "../../module_basic/shared/protocols/worldServer/billiards/MsgHitBall";
import { MsgHitBallComplete } from "../../module_basic/shared/protocols/worldServer/billiards/MsgHitBallComplete";
import { MsgPlayerComesPush } from "../../module_basic/shared/protocols/worldServer/billiards/MsgPlayerComesPush";
import { MsgPlayerDataChangedPush } from "../../module_basic/shared/protocols/worldServer/billiards/MsgPlayerDataChangedPush";
import { MsgPlayerLeavesPush } from "../../module_basic/shared/protocols/worldServer/billiards/MsgPlayerLeavesPush";
import { MsgBallsDataSync } from "../../module_basic/shared/protocols/worldServer/billiards/MsgBallsDataSync";


export class BilliardsGameEvent {
    public static HIT_BALL: string = 'BilliardsGameEvent.HIT_BALL';
    public static HIT_BALL_COMPLETE: string = 'BilliardsGameEvent.HIT_BALL_COMPLETE';
    public static CUE_CHANGED: string = 'BilliardsGameEvent.CUE_CHANGED';
    public static BALL_SYNC: string = 'BilliardsGameEvent.BALL_SYNC';

    public static TURN_PLAYER_CHANGED: string = 'BilliardsGameEvent.TURN_PLAYER_CHANGED';
    public static PLAYER_DATA_CHANGED: string = 'BilliardsGameEvent.PLAYER_DATA_CHANGED';

    public static GAME_BEGIN: string = 'BilliardsGameEvent.GAME_BEBIN';
    public static GAME_OVER: string = 'BilliardsGameEvent.GAME_OVER';

    public static PLAYER_COMES: string = 'BilliardsGameEvent.PLAYER_COMES';
    public static PLAYER_LEAVES: string = 'BilliardsGameEvent.PLAYER_LEAVES';
}


export class BilliardsGameMgr extends GameMgr {
    private static _inst: BilliardsGameMgr;
    public static get inst(): BilliardsGameMgr {
        if (!this._inst) {
            this._inst = new BilliardsGameMgr();
            this._inst.init();
        }
        return this._inst;
    }

    protected _gameData: IBilliardsGameData;
    order: number;
    cueDir: Vec3;
    protected init() {
        super.init();
    }

    public get gameData(): IBilliardsGameData {
        return this._gameData;
    }

    initNetMsgHandlers(conn: WsClient<ServiceType>) {
        super.initNetMsgHandlers(conn);

        conn.listenMsg("billiards/GameDataSyncPush", this.onNet_game_data_sync_push.bind(this));
        conn.listenMsg("billiards/GameDataChangedPush", this.onNet_game_data_changed_push.bind(this));

        conn.listenMsg("billiards/PlayerComesPush", this.onNet_player_comes_push.bind(this));
        conn.listenMsg("billiards/PlayerDataChangedPush", this.onNet_player_data_changed_push.bind(this));
        conn.listenMsg("billiards/PlayerLeavesPush", this.onNet_player_leaves_push.bind(this));

        conn.listenMsg("billiards/GameBeginPush", this.onNet_game_begin_push.bind(this));
        conn.listenMsg("billiards/GameOverPush", this.onNet_game_over_push.bind(this));

        conn.listenMsg("billiards/HitBall", this.onNet_hit_ball_push.bind(this));
        conn.listenMsg("billiards/HitBallComplete", this.onNet_hit_ball_complete_push.bind(this));
        conn.listenMsg("billiards/AdjustCue", this.onNet_adjust_cue_push.bind(this));
    }

    public reset() {
        super.reset();
        this._gameMode = GameMode.SOLO;
        this._gameData = null;
        this.cueDir = null;
        this.cueDir = null;
    }

    public get selfPlayer(): IBilliardsPlayer {
        return this.getPlayer(UserMgr.inst.userId);
    }

    private get selfPlayerIndex(): number {
        return this.getPlayerIndex(UserMgr.inst.userId);
    }

    public get leftUserId(): string {
        if (!this._gameData || this._gameData.players.length == 0) {
            return '';
        }

        if (this.isPlayer) {
            return UserMgr.inst.userId;
        }
        else {
            return this._gameData.players[0].uid;
        }
    }

    public get rightUserId(): string {
        if (!this._gameData || this._gameData.players.length == 0) {
            return '';
        }

        if (this.isPlayer) {
            if (this._gameData.players[0].uid != UserMgr.inst.userId) {
                return this._gameData.players[0].uid;
            }
            else {
                let p = this._gameData.players[1];
                return p ? p.uid : '';
            }
        }
        else {
            let p = this._gameData.players[1];
            return p ? p.uid : '';
        }
    }

    public get isMyTurn(): boolean {
        if (!this._gameData) {
            return false;
        }

        if (!this.isPlayer) {
            return false;
        }
        if (!this._gameData.currentPlayer) {
            return false;
        }
        return this._gameData.currentPlayer == UserMgr.inst.userId;
    }

    public getPlayer(userId: string): IBilliardsPlayer {
        if (!this._gameData || !this._gameData.players) {
            return null;
        }
        for (let i = 0; i < this._gameData.players.length; ++i) {
            let p = this._gameData.players[i];
            if (p.uid == userId) {
                return p;
            }
        }
        return null;
    }

    private getPlayerIndex(userId: string): number {
        for (let i = 0; i < this._gameData.players.length; ++i) {
            if (this._gameData.players[i].uid == userId) {
                return i;
            }
        }
        return -1;
    }

    public isPocketed(ballId: number): boolean {
        if (!this._gameData || !this._gameData.players) {
            return false;
        }
        for (let i = 0; i < this._gameData.players.length; ++i) {
            let p = this._gameData.players[i];
            if (p.pocketedBalls && p.pocketedBalls.indexOf(ballId) != -1) {
                return true;
            }
        }
        return false;
    }

    sendMsg_HitBall(msg: MsgHitBall) {
        WorldMgr.worldConn.sendMsg("billiards/HitBall", msg);
    }

    sendMsg_HitBallComplete(msg: MsgHitBallComplete) {
        WorldMgr.worldConn.sendMsg("billiards/HitBallComplete", msg);
    }

    sendMsg_AdjustCue(msg: MsgAdjustCue) {
        WorldMgr.worldConn.sendMsg("billiards/AdjustCue", msg);
    }

    sendMsg_BallsDataSync(msg: MsgBallsDataSync) {
        WorldMgr.worldConn.sendMsg("billiards/BallsDataSync", msg);
    }

    // ============= MESSAGE HANDLER ============
    /**
     * This message will arrive before login result.
     * 这个消息会在登录成功返回之前收到。
    */
    onNet_game_data_sync_push(msg: MsgGameDataSyncPush) {
        this._gameData = msg;
    }

    onNet_game_data_changed_push(msg: MsgGameDataChangedPush) {
        if (!this._gameData) {
            return;
        }
        if (msg.ballsData !== undefined) {
            this._gameData.ballsData = msg.ballsData;
            if (!this.isMyTurn) {
                director.getScene().emit(BilliardsGameEvent.BALL_SYNC, msg.ballsData);
            }
        }

        if (msg.order !== undefined) {
            this.order = msg.order;
        }
        if (msg.cueDir !== undefined) {
            if (!this.cueDir) {
                this.cueDir = new Vec3();
            }
            this.cueDir.set(msg.cueDir.x, msg.cueDir.y, msg.cueDir.z);
        }

        if (msg.currentPlayer !== undefined) {
            this._gameData.currentPlayer = msg.currentPlayer;
            director.getScene().emit(BilliardsGameEvent.TURN_PLAYER_CHANGED, msg.currentPlayer);
        }
    }

    onNet_player_comes_push(msg: MsgPlayerComesPush) {
        this._gameData.players.push(msg.player);
        director.getScene().emit(BilliardsGameEvent.PLAYER_COMES, msg.player);
    }

    onNet_player_data_changed_push(msg: MsgPlayerDataChangedPush) {
        let p = this.getPlayer(msg.uid);
        if (!p) {
            return;
        }

        if (msg.pocketedBalls != undefined) {
            p.pocketedBalls = msg.pocketedBalls;
        }

        director.getScene().emit(BilliardsGameEvent.PLAYER_DATA_CHANGED, msg);
    }

    onNet_player_leaves_push(msg: MsgPlayerLeavesPush) {
        for (let i = 0; i < this._gameData.players.length; ++i) {
            let p = this._gameData.players[i];
            if (p.uid == msg.uid) {
                this._gameData.players.splice(i, 1);
                director.getScene().emit(BilliardsGameEvent.PLAYER_LEAVES, msg.uid);
                break;
            }
        }
    }

    onNet_game_begin_push(msg: MsgGameBeginPush) {
        if (!this._gameData) {
            return;
        }

        this._gameData.ballsData = null;
        this._gameData.players.forEach(v => {
            v.pocketedBalls = [];
        });

        director.getScene().emit(BilliardsGameEvent.GAME_BEGIN);
    }

    onNet_game_over_push(msg: MsgGameOverPush) {
        director.getScene().emit(BilliardsGameEvent.GAME_OVER, msg);
    }

    onNet_hit_ball_push(msg: MsgHitBall) {
        if (!this._gameData) {
            return;
        }
        if (!this.isMyTurn) {
            director.getScene().emit(BilliardsGameEvent.HIT_BALL, msg.hitImpulse, msg.order);
        }
    }

    onNet_hit_ball_complete_push(msg: MsgHitBallComplete) {
        if (!this._gameData) {
            return;
        }
        director.getScene().emit(BilliardsGameEvent.HIT_BALL_COMPLETE);
    }

    onNet_adjust_cue_push(msg: MsgAdjustCue) {
        if (!this._gameData) {
            return;
        }
        if (msg.direction) {
            if (!this.cueDir) {
                this.cueDir = new Vec3();
            }
            this.cueDir.set(msg.direction.x, msg.direction.y, msg.direction.z);
        }
        if (!this.isMyTurn) {
            director.getScene().emit(BilliardsGameEvent.CUE_CHANGED, msg);
        }
    }
}

SubGameMgr.inst.registerGameMgr('billiards', BilliardsGameMgr.inst);